/**************************************************************************************
*
*	Copyright (C) 2010 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	PulseScript.cpp
*
*	Comments	-	See PulseScript.h
*
**************************************************************************************/

#ifdef __PSX_WINDOWS_WINDOW

#include "../Include/RendererDevice.h"
#include "../Include/Engine.h"
#include "../Include/Renderer.h"
#include "../Include/Dx9Mapping.h"

#include <D3DX9Shader.h>
#pragma comment( lib, "D3Dx9.lib" )

namespace Pulse
{
	RendererDevice::RendererDevice( IDirect3D9 *pD3D, D3DPRESENT_PARAMETERS *pParams,
		::HWND _hwnd, EProcessingType::Type type )
			: m_bIsLost( FALSE ), m_processingType( type ), m_pD3DDev( PSX_NULL ), m_pD3D( pD3D ),
			m_params( *pParams ), m_hWnd( _hwnd )
	{
		m_pD3D->AddRef();
		Initialize();
	}

	RendererDevice::~RendererDevice( void )
	{
		Cleanup();
	}

	void RendererDevice::OnDeviceLost( void )
	{
		HRESULT hRes = m_pD3DDev->TestCooperativeLevel();

		if ( hRes == D3DERR_DEVICELOST ) // Device is lost and can't be reset for now
		{
			if ( !m_bIsLost )
			{
				Engine::GetInstance()->GetRenderer()->OnDeviceLost();
				m_bIsLost = TRUE;
				PSX_PushWarning( "Rendering device lost." );
			}

			Sleep( 100 );
			return;
		}
		else if ( hRes == D3DERR_DEVICENOTRESET ) // Device lost but can now be reset
		{
			PSX_PushLog3( "Device lost and can now be restored." );

			m_bIsLost = FALSE;
			HRESULT hRes = 0;

			PSX_PushLog3( "Restoring device." );
			Reset();
			Engine::GetInstance()->GetRenderer()->OnDeviceReset();

			PSX_PushLog3( "Rendering device successfully restored." );
		}
	}

	BOOL RendererDevice::TestCapabilities( EGraphicsCaps::Type caps, void *pParam )
	{
		switch( caps )
		{
		case EGraphicsCaps::MAX_ANISOTROPY: 
			memcpy(pParam, &m_devCaps.MaxAnisotropy, sizeof(DWORD));
			break;
		case EGraphicsCaps::MIN_ANISOTROPY: 
			PSX_MemSet(pParam, (m_devCaps.TextureFilterCaps & D3DPTFILTERCAPS_MINFANISOTROPIC ? true : false), sizeof(BOOL));
			break;
		case EGraphicsCaps::MAG_ANISOTROPY: 
			PSX_MemSet(pParam, (m_devCaps.TextureFilterCaps & D3DPTFILTERCAPS_MAGFANISOTROPIC ? true : false), sizeof(BOOL));
			break;
		case EGraphicsCaps::MAX_CLIPPLANES: 
			PSX_MemCopy(pParam, &m_devCaps.MaxUserClipPlanes, sizeof(DWORD));
			break;
		case EGraphicsCaps::MAX_INDICES_COUNT: 
			PSX_MemCopy(pParam, &m_devCaps.MaxVertexIndex, sizeof(DWORD));
			break;
		case EGraphicsCaps::MAX_PRIMITIVE_COUNT: 
			PSX_MemCopy(pParam, &m_devCaps.MaxPrimitiveCount, sizeof(DWORD));
			break;
		case EGraphicsCaps::MAX_LIGHTS:	  
			PSX_MemCopy(pParam, &m_devCaps.MaxActiveLights, sizeof(DWORD));
			break;
		case EGraphicsCaps::MAX_RTS:		  
			PSX_MemCopy(pParam, &m_devCaps.NumSimultaneousRTs, sizeof(DWORD));
			break;
		case EGraphicsCaps::MAX_TEXTURE_HEIGHT: 
			PSX_MemCopy(pParam, &m_devCaps.MaxTextureHeight, sizeof(DWORD));
			break;
		case EGraphicsCaps::MAX_TEXTURE_WIDTH: 
			PSX_MemCopy( pParam, &m_devCaps.MaxTextureWidth, sizeof(DWORD) );
			break;
		case EGraphicsCaps::VERTEX_SHADER:  
			PSX_StrCpy( (char*)pParam, D3DXGetVertexShaderProfile(m_pD3DDev), 256 );
			break;
		case EGraphicsCaps::GEOMETRY_SHADER:
			PSX_StrCpy((char*)pParam, PSX_String("no support"), 256);
			return FALSE;
			break;
		case EGraphicsCaps::PIXEL_SHADER:	  
			PSX_StrCpy((char*)pParam, D3DXGetPixelShaderProfile(m_pD3DDev), 256);
			break;
		default:
			PSX_PushWarning( "Unknown or unused device cap." );
			return FALSE;
		}

		return TRUE;
	}

	EErrorCode::Type RendererDevice::Initialize( void )
	{
		DWORD create = 0;
		UINT adapter = D3DADAPTER_DEFAULT;
		D3DDEVTYPE devType = D3DDEVTYPE_HAL;

		switch( m_processingType )
		{
		case EProcessingType::SOFTWARE:
			create = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
			break;
		case EProcessingType::MIXED:
			create = D3DCREATE_MIXED_VERTEXPROCESSING;
			break;
		case EProcessingType::HARDWARE:
			create = D3DCREATE_HARDWARE_VERTEXPROCESSING;
			break;
		case EProcessingType::PURE_HARDWARE:
			create = D3DCREATE_PUREDEVICE | D3DCREATE_HARDWARE_VERTEXPROCESSING;
			break;
		default: 
			PSX_PushWarning( "Unknown vertex processing type. Defaulting to hardware." );
			create = D3DCREATE_HARDWARE_VERTEXPROCESSING;
			break;
		}

		// Create Device
		::HWND hWnd = Engine::GetInstance()->GetTopLevelHWND();
		HRESULT hr = 0;
		if ( FAILED( hr = m_pD3D->CreateDevice( adapter, devType, m_hWnd, create, &m_params, &m_pD3DDev ) ) )
		{
			PSX_PushError( "Failed to create DirectX 9c device renderer. %s", DX9Mapping::GetDXErrorDescription(hr) );
			return EErrorCode::GRAPHICS;
		}

		// Store caps to avoid calling this method again
		if ( FAILED( hr = m_pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &m_devCaps ) ) )
		{
			PSX_PushError( "Failed to get DirectX 9.0c device capabilities." );
			return EErrorCode::GRAPHICS;
		}

		return EErrorCode::NONE;
	}

	void RendererDevice::Cleanup( void )
	{
		PSX_SafeRelease( m_pD3DDev );
		PSX_SafeRelease( m_pD3D );
	}

	void RendererDevice::Reset( void )
	{
		PSX_PushLog3( "Attempting to reset device." );
		HRESULT hr = m_pD3DDev->Reset( &m_params );

		if ( FAILED( hr ) )
			PSX_PushError( "Reset device failed." );

		PSX_PushLog3( "Device reset successful." );
	}
	
	void RendererDevice::Reset( D3DPRESENT_PARAMETERS *pParams )
	{
		m_params = *pParams;
		Reset();
	}

	void RendererDevice::BeginRender( void )
	{
		m_pD3DDev->BeginScene();
	}

	void RendererDevice::EndRender( void )
	{
		m_pD3DDev->EndScene();
		m_pD3DDev->Present( NULL, NULL, NULL, NULL );
	}

}

#endif /* __PSX_WINDOWS_WINDOW */